/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.webos.java.base.fs;

import java.io.EOFException;
import java.io.IOException;
import java.io.RandomAccessFile;
import org.hsqldb.Database;
import org.hsqldb.lib.HsqlByteArrayInputStream;
import org.hsqldb.persist.ScaledRAInterface;
import org.webos.java.base.Session;
import org.webos.java.root.Log;
import org.webos.java.base.SessionManager;

/**
 *
 * @author Kostya
 */
public class DBFileRAFile implements ScaledRAInterface{

    boolean readOnly = false;
    RandomAccessFile file = null;
    long       seekPosition;
    long       realPosition;
    boolean                        bufferDirty = true;
    final byte[]                   buffer;
    final HsqlByteArrayInputStream ba;
    long                           bufferOffset;

    public DBFileRAFile(String aName, Boolean aReadOnly, Object aKey) {
        Log.debug(this, "Creating instance of "+aName+" with "+aKey+", readonly: "+aReadOnly);
        readOnly = aReadOnly;
        int bufferScale = 12;
        int bufferSize = 1 << bufferScale;
        buffer = new byte[bufferSize];
        ba = new HsqlByteArrayInputStream(buffer);
        try {
            FileDescriptor fd = DBFileAccess.getSession(aName).getRootProvider().
                    createPathToFileDescriptor(DBFileAccess.getFileName(aName));
            if(fd!=null){
                fd.setType("text/unknown");
                file = fd.getProvider().getRandomAccessFile(fd, aReadOnly?"r": "rw");
            }
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
    }

    public boolean canAccess(int length) {
        return true;
    }

    public boolean canSeek(long position) {
        return true;
    }

    public Database getDatabase() {
        return null;
    }

    public long length() throws IOException {
        return file.length();
    }

    public void seek(long position) throws IOException {

        if (!readOnly && file.length() < position) {
            long tempSize = position - file.length();

            if (tempSize > 1 << 18) {
                tempSize = 1 << 18;
            }

            byte[] temp = new byte[(int) tempSize];

            try {
                long pos = file.length();

                for (; pos < position - tempSize; pos += tempSize) {
                    file.seek(pos);
                    file.write(temp, 0, (int) tempSize);
                }

                file.seek(pos);
                file.write(temp, 0, (int) (position - pos));

                realPosition = position;
            } catch (IOException e) {
                e.printStackTrace();
                throw e;
            }
        }

        seekPosition = position;
    }

    public long getFilePointer() throws IOException {
        return seekPosition;
    }

    private void readIntoBuffer() throws IOException {

        long filePos    = seekPosition;
        long subOffset  = filePos % buffer.length;
        long fileLength = file.length();
        long readLength = fileLength - (filePos - subOffset);

        try {
            if (readLength <= 0) {
                throw new IOException("read beyond end of file");
            }

            if (readLength > buffer.length) {
                readLength = buffer.length;
            }

            file.seek(filePos - subOffset);
            file.readFully(buffer, 0, (int) readLength);

            bufferOffset = filePos - subOffset;
            realPosition = bufferOffset + readLength;
            bufferDirty  = false;
        } catch (IOException e) {
            resetPointer();
            throw e;
        }
    }

    private void resetPointer() {

        try {
            bufferDirty = true;

            file.seek(seekPosition);

            realPosition = seekPosition;
        } catch (Throwable e) {}
    }
    
    public int read() throws IOException {

        try {
            long fileLength = file.length();

            if (seekPosition >= fileLength) {
                return -1;
            }

            if (bufferDirty || seekPosition < bufferOffset
                    || seekPosition >= bufferOffset + buffer.length) {
                readIntoBuffer();
            } else {
            }

            ba.reset();
            ba.skip(seekPosition - bufferOffset);

            int val = ba.read();

            seekPosition++;

            return val;
        } catch (IOException e) {
            resetPointer();
            e.printStackTrace();
            throw e;
        }
    }

    public void read(byte[] b, int offset, int length) throws IOException {

        try {
            if (bufferDirty || seekPosition < bufferOffset
                    || seekPosition >= bufferOffset + buffer.length) {
                readIntoBuffer();
            } else {
            }

            ba.reset();

            if (seekPosition - bufferOffset
                    != ba.skip(seekPosition - bufferOffset)) {
                throw new EOFException();
            }

            int bytesRead = ba.read(b, offset, length);

            seekPosition += bytesRead;

            if (bytesRead < length) {
                if (seekPosition != realPosition) {
                    file.seek(seekPosition);
                }

                file.readFully(b, offset + bytesRead, length - bytesRead);

                seekPosition += (length - bytesRead);
                realPosition = seekPosition;
            }
        } catch (IOException e) {
            resetPointer();
            e.printStackTrace();
            throw e;
        }
    }

    public void write(byte[] b, int off, int len) throws IOException {

        try {
            if (realPosition != seekPosition) {
                file.seek(seekPosition);

                realPosition = seekPosition;
            }

            if (seekPosition >= bufferOffset
                    && seekPosition < bufferOffset + buffer.length) {
                bufferDirty = true;
            }

            file.write(b, off, len);

            seekPosition += len;
            realPosition = seekPosition;
        } catch (IOException e) {
            resetPointer();
            e.printStackTrace();
            throw e;
        }
    }

    public void writeInt(int i) throws IOException {

        try {
            if (realPosition != seekPosition) {
                file.seek(seekPosition);

                realPosition = seekPosition;
            }

            if (seekPosition >= bufferOffset
                    && seekPosition < bufferOffset + buffer.length) {
                bufferDirty = true;
            }

            file.writeInt(i);

            seekPosition += 4;
            realPosition = seekPosition;
        } catch (IOException e) {
            resetPointer();
            e.printStackTrace();
            throw e;
        }
    }

    public void writeLong(long i) throws IOException {

        try {
            if (realPosition != seekPosition) {
                file.seek(seekPosition);

                realPosition = seekPosition;
            }

            if (seekPosition >= bufferOffset
                    && seekPosition < bufferOffset + buffer.length) {
                bufferDirty = true;
            }

            file.writeLong(i);

            seekPosition += 8;
            realPosition = seekPosition;
        } catch (IOException e) {
            resetPointer();
            e.printStackTrace();
            throw e;
        }
    }

    public long readLong() throws IOException {

        try {
            if (bufferDirty || seekPosition < bufferOffset
                    || seekPosition >= bufferOffset + buffer.length) {
                readIntoBuffer();
            } else {
            }

            ba.reset();

            if (seekPosition - bufferOffset
                    != ba.skip(seekPosition - bufferOffset)) {
                throw new EOFException();
            }

            long val;

            try {
                val = ba.readLong();
            } catch (EOFException e) {
                file.seek(seekPosition);

                val          = file.readLong();
                realPosition = file.getFilePointer();
            }

            seekPosition += 8;

            return val;
        } catch (IOException e) {
            resetPointer();
            e.printStackTrace();
            throw e;
        }
    }

    public void close() throws IOException {
        file.close();
    }

    public boolean isReadOnly() {
        return readOnly;
    }

    public boolean wasNio() {
        return false;
    }

    public int readInt() throws IOException {

        try {
            if (bufferDirty || seekPosition < bufferOffset
                    || seekPosition >= bufferOffset + buffer.length) {
                readIntoBuffer();
            } else {
            }

            ba.reset();

            if (seekPosition - bufferOffset
                    != ba.skip(seekPosition - bufferOffset)) {
                throw new EOFException();
            }

            int val;

            try {
                val = ba.readInt();
            } catch (EOFException e) {
                file.seek(seekPosition);

                val          = file.readInt();
                realPosition = file.getFilePointer();
            }

            seekPosition += 4;

            return val;
        } catch (IOException e) {
            resetPointer();
            e.printStackTrace();
            throw e;
        }
    }

}
